#if !defined(Socket_H)
#define Socket_H

#include "Lock.h"
#include "Thread.h"

#include <string>
#include <sstream>
using namespace std;


// define mapping of error methods
#if defined( _WIN32 )

#if !defined(errno)
#define errno	WSAGetLastError
#endif

#else

// win32 define ioctl for driver control
// so redefine ioctlsocket for normal bsd

#define ioctlsocket ioctl

#endif

////////////////////////////////////////////////////////////////////////////////
// Socket
//
// Purpose: class for reading/writing a socket


class Socket :	private Lock
{
public:
	Socket() :	_socket(INVALID_SOCKET), _port()
	{}


	Socket(Socket &sock) : _socket(INVALID_SOCKET),	_port()
	{
		create(sock._socket, (SOCKADDR &) sock._addrIn);	
	}

	virtual ~Socket()
	{
		close();
	}

	// create/release
	bool create(SOCKET &sock, SOCKADDR &addr)
	{
		if (sock == INVALID_SOCKET)
			return false;

		// get new socket
		memcpy(&_addrIn, (SOCKADDR *)&addr, sizeof(addr));
		memcpy(&_socket, &sock, sizeof(_socket));

		return true;
	}

	void release()
	{
		// close socket if open
		if (_socket != INVALID_SOCKET)
		{
			closesocket(_socket);
			_socket = INVALID_SOCKET;
		}
	}

	bool isValid()
	{
		if (_socket != INVALID_SOCKET)
			return true;
		else
			return false;
	}

	long getPort()
	{
		return _port;
	}

	// open methods
	static bool initSockAddrIn(const string &host, long port, SOCKADDR_IN &addrIn)
	{
		// init socket in struct
		memset(&addrIn,0,sizeof(addrIn));

		// set family
		addrIn.sin_family = AF_INET;

		if (!host.empty())
		{
			// retrieve the host data corresponding to the host name
			LPHOSTENT phost;
			if ((phost = gethostbyname(host.c_str())) != NULL) 
			{
				// assign the socket IP address. 
				memcpy ((char FAR *) &(addrIn.sin_addr), 
						  phost->h_addr, 
						  phost->h_length);
			}
			else
			{
				// get inet address
				addrIn.sin_addr.s_addr = inet_addr(host.c_str());
			}
		}
		else
		{
			// set inet address
			addrIn.sin_addr.s_addr = htonl(INADDR_ANY);
		}

		// set port
		addrIn.sin_port = htons(port);

		return true;
	}

#if 0
	bool open(string host, long port)
	{
		// get socket
		_socket = socket(PF_INET,SOCK_STREAM,0);

		if (_socket == INVALID_SOCKET)
			return false;

		// init address in
		_port = port;
		initSockAddrIn(host, port, _addrIn);

		// connect to socket
		if (connect(_socket, (const SOCKADDR *)&_addrIn, sizeof(_addrIn)) == SOCKET_ERROR)
		{
			int error = getLastError();
			return false;
		}
		
		
		return true;
	}
#endif

	// close
	void close()
	{
		// close socket if open
		if (_socket != INVALID_SOCKET)
		{
			closesocket(_socket);
			_socket = INVALID_SOCKET;
		}
	}

	static int getLastError()
	{
		return WSAGetLastError();
	}

	static bool init()
	{
		#if defined(_WIN32)

		// init startup
		WSADATA WSAData;
		WSAStartup(MAKEWORD(2,0), &WSAData);

		#endif

		return true;
	}

	long getRecieveSize()
	{
		// return not of bytes waiting
		// if fail show none
		long noBytes;
		if (ioControl(FIONREAD, (unsigned long *) &noBytes))
			return noBytes;
		else
			return 0;
	}

	bool setSocketNonBlocking()
	{
		// set socket to non-blocking mode
		// probably need to add more ioctl later
		unsigned long ioctl_opt = -1;
		return ioControl(FIONBIO, &ioctl_opt);
	}

	// recieve/send methods
	long send(string &buffer)
	{
		return send((LPTSTR) buffer.c_str(), buffer.size());
	}

	long send(LPCTSTR buffer, DWORD noToWrite)
	{
		return send((LPTSTR) buffer, noToWrite);
	}

	long send(LPTSTR pBuffer, DWORD noToWrite)
	{
		if (!lock())
			return 0;

		// send to socket
		int noWritten =	::send(_socket, pBuffer, noToWrite, 0);

		unlock();

		// if error show none sent
		if (noWritten == SOCKET_ERROR)
			return 0;
		else
			return noWritten;
	}

	long receive(LPTSTR pBuffer, DWORD noToRead)
	{
		if (!lock())
			return false;

		// recieve from socket
		int noRead = recv(_socket, pBuffer, noToRead, 0);

		// if no recieve error
		if (noRead == SOCKET_ERROR)
		{
			noRead = -1;
		}
		else
		{
			// else terminate buffer
			pBuffer[noRead] = '\0';
		}

		unlock();

		return noRead;
	}

	bool ioControl(long cmd, u_long *argp)
	{
		// perfom action
		long ret = ioctlsocket(_socket,cmd,argp);

		// if success show it
		// else get error show failed
		if (ret == 0)
			return true;
		else
		{
			// show error
			fprintf(stderr,"ioctlsocket failed %d\n",getLastError());

			return false;
		}
	}

	// operators
	operator SOCKET()
	{
		return _socket;
	}

	Socket &operator<<(string &buffer)
	{
		send((LPTSTR)buffer.c_str(), buffer.size());
		return *this;
	}

	Socket &operator>>(stringstream &strm)
	{
		send( (LPTSTR) strm.str().c_str(), strm.str().size() );
		return *this;
	}

	Socket &operator<<(LPCTSTR buffer)
	{
		send( (LPTSTR)buffer, lstrlen(buffer));
		return *this;
	}

	Socket &operator<<(LPTSTR buffer)
	{
		send(buffer, lstrlen(buffer));
		return *this;
	}

	// test if file open
	bool isOpen()
	{
        return (_socket == INVALID_SOCKET)?false:true;
	}

protected:
    SOCKET			_socket;
    SOCKADDR_IN		_addrIn;
    long			_port;
};





#endif